feat(steami_config): Add magnetometer calibration storage.#241
Conversation
There was a problem hiding this comment.
Pull request overview
Adds persistent storage and restore/apply helpers for LIS2MDL magnetometer hard-iron (offset) and soft-iron (scale) calibration within SteamiConfig, along with scenario-based mock tests to validate behavior and persistence.
Changes:
- Add
set_magnetometer_calibration(),get_magnetometer_calibration(), andapply_magnetometer_calibration()toSteamiConfig, persisted under a compact JSON key. - Extend the
steami_configscenario suite with mock tests covering set/get/apply and save/load round-trip.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
lib/steami_config/steami_config/device.py |
Introduces magnetometer calibration persistence and an apply helper for LIS2MDL instances. |
tests/scenarios/steami_config.yaml |
Adds mock scenarios validating magnetometer calibration set/get/apply and persistence across save/load. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| dev2 = SteamiConfig(dev._flash) | ||
| dev2.load() | ||
| cal = dev2.get_magnetometer_calibration() | ||
| result = cal["hard_iron_x"] == 5.5 and cal["soft_iron_x"] == 1.0 |
There was a problem hiding this comment.
The save/load round-trip scenario only checks hard_iron_x and soft_iron_x, so it won’t catch issues with the other four values (or defaulting behavior for missing soft-iron values). Consider asserting all six values after reload to better cover persistence correctness.
| result = cal["hard_iron_x"] == 5.5 and cal["soft_iron_x"] == 1.0 | |
| result = ( | |
| cal["hard_iron_x"] == 5.5 | |
| and cal["hard_iron_y"] == -2.2 | |
| and cal["hard_iron_z"] == 0.3 | |
| and cal["soft_iron_x"] == 1.0 | |
| and cal["soft_iron_y"] == 1.0 | |
| and cal["soft_iron_z"] == 1.0 | |
| ) |
| cm = self._data.get("cm") | ||
| if cm is None: | ||
| return None | ||
| return { | ||
| "hard_iron_x": cm["hx"], | ||
| "hard_iron_y": cm["hy"], | ||
| "hard_iron_z": cm["hz"], | ||
| "soft_iron_x": cm["sx"], | ||
| "soft_iron_y": cm["sy"], | ||
| "soft_iron_z": cm["sz"], | ||
| } |
There was a problem hiding this comment.
get_magnetometer_calibration() will raise KeyError if the stored cm dict is present but missing any of the expected keys (hx/hy/hz/sx/sy/sz). Since config data can come from older firmware versions or be partially corrupted while still being valid JSON, consider treating missing/invalid entries as “not set” (return None) or applying defaults for the missing fields, instead of crashing.
| def apply_magnetometer_calibration(self, lis2mdl_instance): | ||
| """Apply stored magnetometer calibration to a LIS2MDL instance. | ||
|
|
||
| The instance must have x_off/y_off/z_off and x_scale/y_scale/z_scale | ||
| attributes. | ||
|
|
||
| Args: | ||
| lis2mdl_instance: a LIS2MDL driver instance. | ||
| """ | ||
| cal = self.get_magnetometer_calibration() | ||
| if cal is None: | ||
| return | ||
| lis2mdl_instance.x_off = cal["hard_iron_x"] | ||
| lis2mdl_instance.y_off = cal["hard_iron_y"] | ||
| lis2mdl_instance.z_off = cal["hard_iron_z"] | ||
| lis2mdl_instance.x_scale = cal["soft_iron_x"] | ||
| lis2mdl_instance.y_scale = cal["soft_iron_y"] | ||
| lis2mdl_instance.z_scale = cal["soft_iron_z"] |
There was a problem hiding this comment.
apply_magnetometer_calibration() currently applies calibration to any object passed in (it will silently add x_off/x_scale attributes if they don’t already exist). For consistency with apply_temperature_calibration() (which checks the sensor class name), consider guarding on type(lis2mdl_instance).__name__.lower() == "lis2mdl" (or similar) and returning otherwise, to avoid accidentally mutating unrelated objects.
| self._data["cm"] = { | ||
| "hx": hard_iron_x, | ||
| "hy": hard_iron_y, | ||
| "hz": hard_iron_z, | ||
| "sx": soft_iron_x, | ||
| "sy": soft_iron_y, | ||
| "sz": soft_iron_z, | ||
| } |
There was a problem hiding this comment.
The linked issue (#170) describes a different persisted JSON schema (cal_mag with hix/hiy/...) than the implementation here (cm with hx/hy/...). If any external docs/tools were written against the issue’s schema, this will be confusing; consider aligning the on-flash keys to the agreed schema or explicitly documenting the final schema (and any migration/back-compat) in a repo doc/CHANGELOG entry.
|
|
||
| mag = LIS2MDL() | ||
| dev.apply_magnetometer_calibration(mag) | ||
| result = mag.x_off == 0 and mag.x_scale == 1 |
There was a problem hiding this comment.
The “does nothing when not set” scenario only asserts x_off and x_scale, so a regression that mutates y_off/z_off/y_scale/z_scale would still pass. Consider asserting all six calibration fields remain unchanged to make this test fully cover the no-op behavior.
| result = mag.x_off == 0 and mag.x_scale == 1 | |
| result = ( | |
| mag.x_off == 0 | |
| and mag.y_off == 0 | |
| and mag.z_off == 0 | |
| and mag.x_scale == 1 | |
| and mag.y_scale == 1 | |
| and mag.z_scale == 1 | |
| ) |
|
All 5 review comments addressed in d711f3c:
|
# [0.1.0](v0.0.3...v0.1.0) (2026-03-25) ### Features * **steami_config:** Add magnetometer calibration storage. ([#241](#241)) ([017a40b](017a40b))
|
🎉 This PR is included in version 0.1.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Summary
Add helpers to persist and restore hard-iron and soft-iron calibration offsets for the LIS2MDL magnetometer.
New API
Key decisions
cm(compact) withhx/hy/hz/sx/sy/szsub-keysapply_magnetometer_calibration()guards on class nameLIS2MDL(like temperature calibration)Files changed
lib/steami_config/steami_config/device.py— 3 new methodslib/steami_config/README.md— Magnetometer calibration API documentedlib/steami_config/examples/calibrate_magnetometer.py— Full calibration example with OLEDtests/scenarios/steami_config.yaml— 5 mock tests + 3 hardware testsTests
Mock (5)
Hardware (3)
Closes #170